/*
* Copyright 2007-2008 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions
* and limitations under the License.
*/

package net.liftweb.widgets.trees.treetable

import scala.xml.{NodeSeq, Node, PCData, Text, Unparsed, NodeBuffer, Elem}
import scala.collection.mutable.HashMap

import net.liftweb.widgets.trees.model.{TreeModel,NodeInfo}
import net.liftweb.widgets.trees.util.BinderMethod

import java.lang.reflect.Method

import net.liftweb.http.S

/*
 * 
 */
 
class TreeTableOptions {
  val showRoot : Boolean = false
  val collapseUnderLevel : Option [Int] = None  // None => don't collapse
  val striped : Boolean = false
  val highlight: Boolean = true
  val column: Int = 0
  val onselect : Option [String] = None
}

/*
 * 
 */
 
object TreeTable {
  /**
   * register the resources with lift (typically in boot)
   */
   
  def init() {  
    import net.liftweb.http.ResourceServer  
   
    ResourceServer.allow({  
      case "treetable" :: _ => true  
    })
    
    println("register tree-table")  
  }
  
  /**
   * render a tree-table using this jQuery plugin: http://www.hanpau.com/jquery/unobtrusivetreetable.php
   */
  
  def render [A <: AnyRef, B <: AnyRef] (treeModel : TreeModel [A, B], options : TreeTableOptions, rowsBinder : Function2[NodeSeq, Nothing, NodeSeq]*)
    : (NodeSeq => NodeSeq) =
  {
    val binderMethod = new BinderMethod (rowsBinder : _*)
      
    // flatten the tree model
    val rows : List [Pair [AnyRef, NodeInfo]] = if (options.showRoot) treeModel.preorder else treeModel.preorder.drop (1)

    // process the tag <tr rowtype="_rowtype_"></tr>

    def transformRow (ns: Iterable[Node]): Seq[Node] = {
      // search the diferent row type in the xhtml (ns) and map their respective sub-template
      val templates = new HashMap [String, Node] ()

      for (val tr <- ns) {
        tr match {
          case rowXml @ <tr>{ ns @ _* }</tr> if rowXml.attribute("rowtype").isDefined => 
                  rowXml.attribute("rowtype").elements.next match {
                      case Text (rowType) => templates += (rowType-> rowXml)
                      case _ => println ("rowtype attribute defined but with no value " + tr)
                  }
                  
          case _ => ()
        }
      }
      
      // process the in-memory row )the model)

      val zs = new NodeBuffer();
    
      for (row <- rows)
      {
        // 
        val rowType = BinderMethod.rowTypeKey (row._1.getClass)

        // println (rowType)
        
        try {
          // search the row template
          val template = templates (rowType)
            
          // search the row-binder function
          val binder = binderMethod.get (rowType)

          val parameters = new Array [Object] (2)
          parameters (0) = template
          parameters (1) = row._1
          
          // invoke the row binder
          zs &+ binder._2.invoke (binder._1, parameters)
        }
        catch {
          case nseex : NoSuchElementException  => println ("Either template or binder not defined for row type " + rowType)
        }
      }

      zs
    }

    // TODO: tbody id="treetable" -> use the id of the templates or generate automatically one
    // Note: need to modify jqTreeTable.js first

    def transformOneNode (n: Node):Iterable[Node] = {
      n match {
        case x @ <tbody>{ ns @ _* }</tbody> =>
            <tbody id="treetable">{transformRow (ns)}</tbody>
        case Elem(namespace, label, attrs, scp, ns @ _*) =>
            Elem(namespace, label, attrs, scp, transform( ns ):_* )
        case z =>
            z 
      }
    }
    
    //

    def renderJsMap () : String = {
      def _generateMap (_rows : List [Pair [AnyRef, NodeInfo]]) : String = {
        _rows match {
          case List (last) => last._2.parentId.toString
          case head :: tail => head._2.parentId.toString + ", " + _generateMap (tail)
        }
      }
    
      _generateMap (rows)
    }

    //
    
    def _generateCollapse (_rows : List [Pair [Any, NodeInfo]]) : String = {
      _rows match {
        case Nil => ""
        case List (last) => last._2.id.toString
        case head :: tail => head._2.id.toString + ", " + _generateCollapse (tail)
      }
    }

    //
    
    def renderCollapse () : String = {
      val rowsToCollapse = rows.filter (row => row._2.isCollapsed) 
    
      // println (rowsToCollapse)
    
      rowsToCollapse match {
        case Nil => "false"
        case _ => "[" + _generateCollapse (rowsToCollapse) + "]" 
      }
    }
    
    //
    
    def renderCollapseLevel (level : Int) : String = {
      val rowsToCollapse = rows.filter (row => row._2.level == level) // jqQuery take care of sub-rows
    
      // println (rowsToCollapse)
    
      "[" + _generateCollapse (rowsToCollapse) + "]"
    }
    
    //

    def renderJs () : String = {
    
      var sz = new StringBuilder (500)
    
      sz.append ( """
/* <![CDATA[ */		      
jQuery(function(){
  var map = [ 
  """)
  
      sz.append (renderJsMap ())
  
      sz.append ("""
    ] ;
    var options = {
"""
      )
  
      sz.append ("      openImg: \""     + S.contextPath + "/classpath/treetable/themes/default/tv-collapsable.gif\",\n")  
      sz.append ("      shutImg: \""     + S.contextPath + "/classpath/treetable/themes/default/tv-expandable.gif\",\n")  
      sz.append ("      leafImg: \""     + S.contextPath + "/classpath/treetable/themes/default/tv-item.gif\",\n")
      sz.append ("      lastOpenImg: \"" + S.contextPath + "/classpath/treetable/themes/default/tv-collapsable-last.gif\",\n") 
      sz.append ("      lastShutImg: \"" + S.contextPath + "/classpath/treetable/themes/default/tv-expandable-last.gif\",\n")
      sz.append ("      lastLeafImg: \"" + S.contextPath + "/classpath/treetable/themes/default/tv-item-last.gif\",\n")
      sz.append ("      vertLineImg: \"" + S.contextPath + "/classpath/treetable/themes/default/vertline.gif\",\n")
      sz.append ("      blankImg: \""    + S.contextPath + "/classpath/treetable/themes/default/blank.gif\",\n")
  
      // 
      options.collapseUnderLevel match {
        case None => sz.append ("      collapse: " + renderCollapse () + ",\n")
        case Some(level) => sz.append ("      collapse: " + renderCollapseLevel (level) + ",\n")
      }
  
      sz.append ("      column: " + options.column + ",\n")        
      sz.append ("      striped: " + options.striped + ",\n")
  
      options.onselect match {
        case Some (function) => sz.append ("      onselect: " + function + ",\n")
        case None => 
      }
  
      sz.append ("      highlight: " + options.highlight + "\n")
      
      sz.append ("""  
    };
    jQuery("#treetable").jqTreeTable(map, options);
});
/* ]]> */    
    """)
    
      sz.toString
    }
  
    //
    def transform (xhtml : NodeSeq) : NodeSeq = {
      val zs = new NodeBuffer();
      for(val z <- xhtml) {zs &+ transformOneNode (z)}

      zs
    }
    
    //
    def ret (xhtml : NodeSeq) : NodeSeq = {
      val js = renderJs ()
    
      <xml:group>
      <head>
        <link href="/classpath/treetable/themes/default/jQTreeTable.css" type="text/css" rel="stylesheet" />
        <script type="text/javascript" src="/classpath/treetable/jQTreeTable.js"></script>
        
        <script type="text/javascript">
        {Unparsed (js)}
        </script>
      </head>
      {transform (xhtml)}
      </xml:group>
    }

    ret
  }
    
}
